﻿using System;
using System.Collections.Specialized;
using System.Configuration;
using System.IO;
using System.Reflection;

namespace DynamicConfigurationManager
{
	/// <summary>
	/// A ConfigurationManager replacement which tracks live changes to app.config
	/// </summary>
	public static class DynamicConfigurationManager
    {
		/// <summary>
		/// Occurs when a change to the configuration file has been deteted.
		/// </summary>
		/// <remarks>When a change to the configuration file is detected, configuration details are refreshed and <i>then</i> this event is fired.</remarks>
		public static event EventHandler ConfigurationChanged;

		private static void OnConfigurationChanged()
		{
			// Local copy to be thread-safe
			EventHandler locum = ConfigurationChanged;

			// Execute if not null
			if (locum != null)
			{
				locum(null, EventArgs.Empty);
			}
		}

		/// <summary>
		/// Initialised the <see cref="DynamicConfigurationManager"/>.
		/// </summary>
		public static void Initialise()
		{
			Console.Out.WriteLine("DynamicConfigurationManager Initialised");
		}

		private static readonly FileSystemWatcher _fsw;
		/// <summary>
		/// <see cref="DynamicConfigurationManager"/> static constructor
		/// </summary>
		static DynamicConfigurationManager()
		{
			_fsw = new FileSystemWatcher(Path.GetDirectoryName( _configFileName));

			// Filter watcher with config file name, so we don't have to watch every file in the directory
			_fsw.Filter = Path.GetFileName(_configFileName);

			_fsw.Changed += Fsw_Changed;
			_fsw.EnableRaisingEvents = true;

		}

		private static void Fsw_Changed(object sender, FileSystemEventArgs e)
		{
			// If the config file has been altered or created, refresh all the configuration sections
			if((e.FullPath == _configFileName) && ((e.ChangeType == WatcherChangeTypes.Changed) || (e.ChangeType == WatcherChangeTypes.Created)))
			{
				Configuration conf = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

				foreach(ConfigurationSection section in conf.Sections)
				{
					ConfigurationManager.RefreshSection(section.SectionInformation.Name);
				}

				#region Old code for manual search for customer configurations
				//XDocument doc = XDocument.Load(File.OpenRead(_configFileName));

				//IEnumerable<XElement> sections = from section in doc.Descendants("configuration").Descendants("configSections").Descendants("section") select section;

				//foreach (XElement section in sections)
				//{
				//    string name = (from attribute in section.Attributes() where attribute.Name == "name" select attribute.Value).First();
				//    if (string.IsNullOrWhiteSpace(name) == false)
				//    {
				//        ConfigurationManager.RefreshSection(name);
				//    }
				//}

				//ConfigurationManager.RefreshSection("appSettings");
				//ConfigurationManager.RefreshSection("connectionStrings");
				#endregion

				// Finally fire off configuration changed event
				OnConfigurationChanged();
			}
		}

        private static readonly string _configFileName = GetDefaultConfigFileName();

        private static string GetDefaultConfigFileName()
        {
			// Figure out the config filename from the calling assembly
			Assembly assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();

        	return assembly.Location + ".config"; 
        }


		/// <summary>
		/// Gets the <see cref="System.Configuration.AppSettingsSection"/> data for the current application's default configuration.
		/// </summary>
		/// <returns>
		///  Returns a <see cref="System.Collections.Specialized.NameValueCollection"/> object that contains the contents of the
		///  <see cref="System.Configuration.AppSettingsSection"/> object for the current application's default configuration.
		/// </returns>
		/// <exception cref="System.Configuration.ConfigurationErrorsException">
		/// Could not retrieve a <see cref="System.Collections.Specialized.NameValueCollection"/> object with the application settings data.
		/// </exception>
		public static NameValueCollection AppSettings
		{
			get
			{
				// Get the application configuration file.
				Configuration cfg = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
				AppSettingsSection appSettings = (AppSettingsSection)cfg.GetSection("appSettings");

				NameValueCollection nvc = new NameValueCollection();

				foreach (KeyValueConfigurationElement element in appSettings.Settings)
				{
					nvc.Add(element.Key, element.Value);
				}

				return nvc;
			}
		}

	
		/// <summary>
		/// Gets the <see cref="System.Configuration.ConnectionStringsSection"/> data for the current application's default configuration.
		/// </summary>
		/// <returns>
		/// Returns a <see cref="System.Configuration.ConnectionStringSettingsCollection"/> object that contains the contents of the 
		/// <see cref="System.Configuration.ConnectionStringsSection"/> object for the current application's default configuration.
		/// </returns>
		/// <exception cref="System.Configuration.ConfigurationErrorsException">
		/// Could not retrieve a <see cref="System.Configuration.ConnectionStringSettingsCollection"/> object.
		/// </exception>
		public static ConnectionStringSettingsCollection ConnectionStrings
		{
			get
			{
				// Get the application configuration file.
				Configuration cfg = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
				ConnectionStringsSection connectionStrings = (ConnectionStringsSection)cfg.GetSection("appSettings");

				return connectionStrings.ConnectionStrings;
			}
		}

		/// <summary>
		///  Retrieves a specified configuration section for the current application's default configuration.
		/// </summary>
		/// <param name="sectionName">The configuration section path and name.</param>
		/// <returns>
		/// The specified <see cref="System.Configuration.ConfigurationSection"/> object, or null if the section does not exist.
		/// </returns>
		/// <exception cref="System.Configuration.ConfigurationErrorsException">A configuration file could not be loaded.</exception>
		public static object GetSection(string sectionName)
		{
			Configuration cfg = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
			return cfg.GetSection(sectionName);
		}


		/// <summary>
		/// Opens the configuration file for the current application as a <see cref="System.Configuration.Configuration"/> object.
		/// </summary>
		/// <param name="userLevel">
		/// The <see cref="System.Configuration.ConfigurationUserLevel"/> for which you are opening the configuration.
		/// </param>
		/// <returns>A <see cref="System.Configuration.Configuration"/> object.</returns>
		/// <exception cref="System.Configuration.ConfigurationErrorsException">A configuration file could not be loaded.</exception>
		public static Configuration OpenExeConfiguration(ConfigurationUserLevel userLevel)
		{
			return ConfigurationManager.OpenExeConfiguration(userLevel);
		}

		/// <summary>
		/// Opens the specified client configuration file as a <see cref="System.Configuration.Configuration"/> object.
		/// </summary>
		/// <param name="exePath">The path of the configuration file. The configuration file resides in the same directory
		/// as the executable file.</param>
		/// <returns>A <see cref="System.Configuration.Configuration"/> object.</returns>
		/// <exception cref="System.Configuration.ConfigurationErrorsException">A configuration file could not be loaded.</exception>
		public static Configuration OpenExeConfiguration(string exePath)
		{
			return ConfigurationManager.OpenExeConfiguration(exePath);
		}

		/// <summary>
		/// Opens the machine configuration file on the current computer as a <see cref="System.Configuration.Configuration"/> object.
		/// </summary>
		/// <returns>A <see cref="System.Configuration.Configuration"/> object.</returns>
		/// <exception cref="System.Configuration.ConfigurationErrorsException">A configuration file could not be loaded.</exception>
		public static Configuration OpenMachineConfiguration()
		{
			return ConfigurationManager.OpenMachineConfiguration();
		}

		/// <summary>
		///  Opens the specified client configuration file as a <see cref="System.Configuration.Configuration"/> object that 
		///  uses the specified file mapping and user level.
		/// </summary>
		/// <param name="fileMap">
		/// A <see cref="System.Configuration.ExeConfigurationFileMap"/> object that references configuration file to use  instead of 
		/// the application default configuration file.
		/// </param>
		/// <param name="userLevel">
		/// The <see cref="System.Configuration.ConfigurationUserLevel"/> object for which you are opening the configuration.
		/// </param>
		/// <returns>A <see cref="System.Configuration.Configuration"/> object.</returns>
		/// <exception cref="System.Configuration.ConfigurationErrorsException">A configuration file could not be loaded.</exception>
		public static Configuration OpenMappedExeConfiguration(ExeConfigurationFileMap fileMap, ConfigurationUserLevel userLevel)
		{
			return ConfigurationManager.OpenMappedExeConfiguration(fileMap, userLevel);
		}

		
		/// <summary>
		/// Opens the machine configuration file as a <see cref="System.Configuration.Configuration"/> object that uses the specified 
		/// file mapping.
		/// </summary>
		/// <param name="fileMap"> An <see cref="System.Configuration.ExeConfigurationFileMap"/> object that references configuration 
		/// file to use instead of the application default configuration file.</param>
		/// <returns> A System.Configuration.Configuration object.</returns>
		/// <exception cref="System.Configuration.ConfigurationErrorsException">A configuration file could not be loaded.</exception>
		public static Configuration OpenMappedMachineConfiguration(ConfigurationFileMap fileMap)
		{
			return ConfigurationManager.OpenMappedMachineConfiguration(fileMap);
		}

		/// <summary>
		/// Refreshes the named section so the next time that it is retrieved it will be re-read from disk.
		/// </summary>
		/// <param name="sectionName">
		/// The configuration section name or the configuration path and section name of the section to refresh.
		/// </param>
		public static void RefreshSection(string sectionName)
		{
			ConfigurationManager.RefreshSection(sectionName);
		}

    }
}
